﻿#region MIT License
/*

Copyright (C) 2011 by Timothy A. Barela

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/
#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;

namespace Sharpener
{
	internal static class InstructionHelper
	{
		public static bool IsBranch( Instruction instruction )
		{
			return instruction.OpCode.FlowControl == FlowControl.Branch ||
				instruction.OpCode.FlowControl == FlowControl.Cond_Branch;

		}
		
		/// <summary>
		/// Creates an Instruction instance, that is identical to the source.
		/// </summary>
		public static Instruction Clone( Instruction source )
		{
			//we must create a nop! there is no public ctor, and all other Create() overloads
			//require/validate operands and will throw... 
			var dst = Instruction.Create(OpCodes.Nop);

			dst.Offset = source.Offset;
			dst.OpCode = source.OpCode;
			dst.Operand = source.Operand;

			return dst;
		}

		public static List<Instruction> CloneList( IEnumerable<Instruction> source )
		{
			return CloneList(new List<Instruction>(source));
		}

		public static List<Instruction> CloneList( List<Instruction> source )
		{
			var dst =  source.ConvertAll<Instruction>(Clone);

			remapClonedBranchTargets(source, dst);

			return dst;
		}

		//cloned lists will still branch to instructions in the original list-- this method fixes that.
		//This method assumes that destination is an exact copy/clone of source!
		static void remapClonedBranchTargets( List<Instruction> source, List<Instruction> destination )
		{
			for(int i = 0; i < source.Count; i++)
			{
				var srcInstruction = source[i];
				
				if( IsBranch( srcInstruction ) )
				{
					//must remap!
					int index = source.IndexOf(srcInstruction.Operand as Instruction);

					if(index == -1)
						throw new Exception("Operand target instruction was not found in source buffer.");

					destination[i].Operand = destination[index];
				}
			}
		}

		/// <summary>
		/// Finds and replaces all branches pointing to a specific target with a new target.  
		/// </summary>
		public static void RemapBranchTargets( IList<Instruction> instructions, Instruction oldTarget, Instruction newTarget )
		{
			foreach(var inst in instructions)
			{
				if(IsBranch(inst))
				{
					if(inst.Operand == oldTarget)
						inst.Operand = newTarget;
				}
			}
		}

		/// <summary>
		/// Removes repeating nop code sequences.
		/// </summary>
		public static void FoldNops( IList<Instruction> instructions )
		{
			int i = 0;

			while(i < instructions.Count - 1)
			{
				var current = instructions[i];

				if(current.OpCode == OpCodes.Nop)
				{
					var next = instructions[i + 1];

					if(next.OpCode == OpCodes.Nop)
					{
						instructions.RemoveAt(i + 1);
						RemapBranchTargets(instructions, next, current);
						continue;
					}
				}

				i++;
			}
		}

		/// <summary>
		/// Removes br's that jump to the very next instruction. 
		/// </summary>
		public static void FoldBranches( IList<Instruction> instructions )
		{
			int i = 0;

			while(i<instructions.Count-1)
			{
				var current = instructions[i];

				if(current.OpCode == OpCodes.Br)
				{
					var branchTarget = current.Operand as Instruction;
										
					if(instructions[i + 1] == branchTarget)
					{
						//superfluous branch
						instructions.RemoveAt(i);
						RemapBranchTargets(instructions, current, branchTarget);
						continue;
					}
				}

				i++;
			}
		}
	}
}